﻿using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using GMap.NET;
using GMap.NET.WindowsPresentation;
using ProjectorMapApp.CustomMarkers;
using System.Messaging;
using System.Runtime.InteropServices;


namespace ProjectorMapApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        PointLatLng previousPosition = new PointLatLng(-36.866667, 174.766667);
        PointLatLng currentPosition = new PointLatLng(-36.866667, 174.766667);
        PointLatLng draggingPosition = new PointLatLng(-36.866667, 174.766667);

        const int SQUARE_Z_INDEX = -1;

        string machineName = "ProjectorApp";//System.Environment.MachineName;
        public System.Windows.Forms.Timer idleTimer = new System.Windows.Forms.Timer();//using windows forms as to run on main thread
        System.Timers.Timer checkQueueTimer = new System.Timers.Timer();
        System.Windows.Forms.Timer markerRedrawTimer = new System.Windows.Forms.Timer();
        System.Windows.Forms.Timer pushNewStoryTimer = new System.Windows.Forms.Timer();
        System.Windows.Forms.Timer showStoryBubbleTimer = new System.Windows.Forms.Timer();


        ArrayList categoriesArrayList = new ArrayList();//for storing category info from server
        ArrayList timeSlotArrayList = new ArrayList(); //store details about available timeslots from server
        ArrayList storiesArrayList = new ArrayList(); //downloaded stories
        ArrayList recentStoriesArrayList = new ArrayList();//store recently added stories here
        ArrayList museumStoriesArrayList = new ArrayList();

        const string LOADSTORYREQUEST = "rn=loadStories";
        const string LOADCATEGORYREQUEST = "rn=loadCategories";
        const string LOADTIMESLOTSREQUEST = "rn=loadTimeSlots";
        const string LOADRECENTSTORYREQUEST = "rn=loadRecentStories";
        const string LOADMUSEUMSTORYREQUEST = "rn=loadMuseumStories";

        int MaxNumberRecentStoriesToShowAtATime = Properties.Settings.Default.MaxNumberRecentStoriesToShowAtATime;
        int MaxNumberMuseumStoriesToShowAtATime = Properties.Settings.Default.MaxNumberMuseumStoriesToShowAtATime;
        int MaxNumberRandomStoriesToShowAtATime = Properties.Settings.Default.MaxNumberRandomStoriesToShowAtATime;
        int MaxNumberTotalStoriesToShowAtATime;

        string RequestPageURL = Properties.Settings.Default.RequestPageURL;
        int storyCycleInterval = Properties.Settings.Default.storyCycleInterval;
        public static string iconsStarterPath = Properties.Settings.Default.iconsPathStarterURL;

        Story[] timerAddStories;//to help with adding marker timer
        int timerAddStoryIndex;//

        bool noRecentStoriesForALongTime = true;

        //The . in the queueName represents localhost
        //private const string queueName = @".\Private$\group26";
        private string queueName = "FormatName:DIRECT=OS:" + Properties.Settings.Default.messageQueueHostName + @"\private$\group26";
        //private string queueName = "FormatName:DIRECT=OS:" + "AmritUni-PC" + @"\private$\group26";
        MessageQueue queue = null;

        bool thereIsNewStoryWaitingToBeAdded = false;
        Story messageReceiveStory;

        bool avoidTimerConflicts = true;

        public MainWindow()
        {
            InitializeComponent();
            
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //auckland position
            PointLatLng mapCenterPosition = new PointLatLng(Properties.Settings.Default.mapCenterLattitude, Properties.Settings.Default.mapCenterLongitude);
            // config map
            MainMap.MapType = MapType.NearMap;
            MainMap.ShowCenter = false; //hide the crosshair
            MainMap.AllowDrop = true;
            MainMap.MaxZoom = Properties.Settings.Default.MapDefaultZoom;
            MainMap.MinZoom = Properties.Settings.Default.MapDefaultZoom;
            MainMap.Zoom = Properties.Settings.Default.MapDefaultZoom;
            MainMap.Position = mapCenterPosition;

            MainMap.CacheLocation = AppDomain.CurrentDomain.BaseDirectory; //bin/debug folder
            MainMap.Manager.Mode = AccessMode.ServerAndCache;

            MaxNumberTotalStoriesToShowAtATime = MaxNumberRandomStoriesToShowAtATime + MaxNumberMuseumStoriesToShowAtATime
            + MaxNumberRecentStoriesToShowAtATime;


            if (initialiseCategories())
            {
               // initialiseMarkerIconsWithTags();
            }
            else
            {
                //TODO : what to do in case false returned (there was some error!)

            }

            if (initialiseTimeSlots())
            {
               // initialiseTimeSlotIconsWithTags();
            }
            else
            {
                //TODO : what to do in case false returned (there was some error!)
            }

            if (MaxNumberMuseumStoriesToShowAtATime > 0)
            {
                fetchMuseumStories();
            }
            //to download AND refresh the markers on the map (reloadOnly = false)
            addDownloadedMarkersToMap(false);


            idleTimer.Interval = storyCycleInterval;
            idleTimer.Tick += new EventHandler(idleTimer_Tick);
            idleTimer.Start();//to redo events after # time elapse

            pushNewStoryTimer.Interval = 50;
            pushNewStoryTimer.Tick += new EventHandler(pushNewStoryTimer_Tick);
            pushNewStoryTimer.Start();

            showStoryBubbleTimer.Interval = 30000; //30 seconds for now
            showStoryBubbleTimer.Tick += new EventHandler(showStoryBubbleTimer_Tick);
            showStoryBubbleTimer.Start();

            if (CreateQueue())
            {
                //removeMessages();//uncomment if you want to purge all messages in the queue
            }
            
        }

        

        /// <summary>
        /// Removes current stories and reloads another lot
        /// Uses a timer to perform the actual reload after 50ms
        /// </summary>
        private void reloadStories()
        {
            if (!markerRedrawTimer.Enabled)
            {
                //MainMap.Markers.Clear(); //to clear current story markers on map
                markerRedrawTimer.Tick += new EventHandler(removeMarkerTimer_Tick);
                markerRedrawTimer.Interval = 1;
                markerRedrawTimer.Start();
            }
        }



        /// <summary>
        /// uses the "downloadRequestFromDB()" method to provide a string containing all the categories
        /// This string is processed by this method to produce an arraylist of categories
        /// </summary>
        /// <returns></returns>
        private bool initialiseCategories()
        {
            string downloadedCategories = downloadRequestFromDB(LOADCATEGORYREQUEST);
            bool returnVal = false;
            if (downloadedCategories.Equals("0001234")) //means an exception occurred when downloading categories
            {
                //TODO : do something here in case server not available...

                returnVal = false;
            }
            else
            {
                if (downloadedCategories.Equals(""))
                {
                    //TODO : do something here in case empty string returned (no cats in db)

                    returnVal = false;
                }
                else
                {
                    string[] categoriesArray = downloadedCategories.Split('\'');
                    int i = 0;
                    bool adminCtg;
                    while (i < categoriesArray.Length - 1)
                    {
                        adminCtg = false;
                        if (categoriesArray[i + 3].ToString().Equals("1") || categoriesArray[i + 3].ToLower().Equals("true")) { adminCtg = true; }
                        categoriesArrayList.Add(new Category(Convert.ToInt32(categoriesArray[i]), categoriesArray[i + 1].ToString(), categoriesArray[i + 2].ToString(), adminCtg, categoriesArray[i + 4].ToString(),
                            categoriesArray[i + 5].ToString(), categoriesArray[i + 6].ToString()));
                        i += 7;
                    }

                    returnVal = true;
                }
            }
            return returnVal;
        }

        // <summary>
        /// uses the "downloadRequestFromDB()" method to provide a string containing all the timeslots
        /// This string is processed by this method to produce an arraylist of timeslots
        /// </summary>
        /// <returns></returns>
        private bool initialiseTimeSlots()
        {
            string downloadedTimeSlots = downloadRequestFromDB(LOADTIMESLOTSREQUEST);
            bool returnVal = false;
            if (downloadedTimeSlots.Equals("0001234")) //means an exception occurred when downloading categories
            {
                //TODO : do something here in case server not available...
                returnVal = false;
            }
            else
            {
                if (downloadedTimeSlots.Equals(""))
                {
                    returnVal = false;
                }
                else
                {
                    string[] timeSlotsArray = downloadedTimeSlots.Split('\'');
                    int i = 0;
                    while (i < timeSlotsArray.Length - 1)
                    {
                        timeSlotArrayList.Add(new TimeSlot(Convert.ToInt32(timeSlotsArray[i]), timeSlotsArray[i + 1].ToString(), timeSlotsArray[i + 2].ToString()));
                        i += 3;
                    }

                    returnVal = true;
                }
            }
            return returnVal;
        }

        /// <summary>
        /// takes the result (string array) from downloadRequestFromDB() method, 
        /// splits them and creates a story object array out of it.
        /// This array is used to manipulate which stories should be displayed.
        /// This method also calls the addMarkerToMap() method which draws the stories
        /// to the map
        /// </summary>
        private void fetchMuseumStories()
        {
            string downloadedStories = "";

            downloadedStories = downloadRequestFromDB(LOADMUSEUMSTORYREQUEST);
            //MessageBox.Show(downloadedStories);


            if (downloadedStories.Equals("0001234")) //means an exception occurred when downloading stories
            {

            }
            else
            {
                if (downloadedStories.Equals(""))
                {

                }
                else
                {
                    museumStoriesArrayList = new ArrayList();
                    string[] museumStoriesArray = downloadedStories.Split('\'');
                    int i = 0;
                    while (i < museumStoriesArray.Length - 1)
                    {
                        museumStoriesArrayList.Add(new Story(Convert.ToInt32(museumStoriesArray[i]), Convert.ToInt32(museumStoriesArray[i + 1]),
                            Convert.ToInt32(museumStoriesArray[i + 2]), Convert.ToDouble(museumStoriesArray[i + 3]),
                            Convert.ToDouble(museumStoriesArray[i + 4]), museumStoriesArray[i + 5], false));
                        //MessageBox.Show("new story added to museumStoriesArrayList!");

                        i += 6;
                    }
                }
            }
        }

        // <summary>
        /// uses the "downloadRequestFromDB()" method to provide a string containing all the recent stories
        /// This string is processed by this method to produce an arraylist of recent stories
        /// </summary>
        /// <returns></returns>
        private void fetchRecentStories()
        {
            //TODO: clear after a certain time frame
            ////add previously recent stories into the main list
            //if (recentStoriesArrayList != null)
            //{
            //    storiesArrayList.AddRange(recentStoriesArrayList);
            //}
            //recentStoriesArrayList = new ArrayList(); //clear any previously 'recent' stories

            //string downloadedRecentStories = downloadRequestFromDB(LOADRECENTSTORYREQUEST);
            //if (downloadedRecentStories.Equals("0001234")) //means an exception occurred when downloading categories
            //{
            //    //TODO : do something here in case server not available...
            //}
            //else
            //{
            //    if (downloadedRecentStories.Equals(""))
            //    {
            //    }
            //    else
            //    {
            //        string[] recentStoriesArray = downloadedRecentStories.Split('\'');
            //        int i = 0;
            //        while (i < recentStoriesArray.Length - 1)
            //        {
            //            int index = 0;
            //            bool found = false;


            //            foreach (Story story in recentStoriesArrayList)
            //            {
            //                if (story.StoryID == Convert.ToInt32(recentStoriesArray[i]))
            //                {
            //                    found = true;
            //                }
            //                index++;
            //            }


            //            index = 0;
            //            foreach (Story story in storiesArrayList)
            //            {
            //                if (story.StoryID == Convert.ToInt32(recentStoriesArray[i]))
            //                {
            //                    found = true;
            //                }
            //                index++;
            //            }
                        

            //            //if it wasn't found at recent stories or stories arraylist, add it to recent stories arraylist
            //            if (!found)
            //            {
            //                recentStoriesArrayList.Add(new Story(Convert.ToInt32(recentStoriesArray[i]), Convert.ToInt32(recentStoriesArray[i + 1]),
            //                    Convert.ToInt32(recentStoriesArray[i + 2]), Convert.ToDouble(recentStoriesArray[i + 3]),
            //                    Convert.ToDouble(recentStoriesArray[i + 4]), recentStoriesArray[i + 5], true));
            //                //Console.WriteLine("new story was found_" + recentStoriesArray[i + 5].ToString());
            //                //MessageBox.Show("new story added to recentStoriesArrayList!");
            //            }

            //            i += 6;
            //        }
            //    }
            //}

            if (messageReceiveStory != null)
            {
                //check if size of recent stories is right
                if (recentStoriesArrayList.Count >= MaxNumberRecentStoriesToShowAtATime)
                {
                    recentStoriesArrayList.RemoveAt(0);//remove first item,--which has been there the longest
                }

                messageReceiveStory.ShowBubble = true;
                messageReceiveStory.LastView = DateTime.Now;
                recentStoriesArrayList.Add(messageReceiveStory); //add new story to the end of the list
                messageReceiveStory = null;
            }
            
            //Console.WriteLine("new story was found_" + recentStoriesArray[i + 5].ToString());
            //MessageBox.Show("new story added to recentStoriesArrayList!");

        }




        /// <summary>
        /// takes the result (string array) from downloadRequestFromDB() method, 
        /// splits them and creates a story object array out of it.
        /// This array is used to manipulate which stories should be displayed.
        /// This method also calls the addMarkerToMap() method which draws the stories
        /// to the map
        /// </summary>
        /// <param name="onlyRefresh">Whether stories should be refreshed or re-fetched</param>
        private void addDownloadedMarkersToMap(bool onlyRefresh)
        {
            string downloadedStories = "";
            if (!onlyRefresh)
            {
                downloadedStories = downloadRequestFromDB(LOADSTORYREQUEST);
                //MessageBox.Show(downloadedStories);
            }
            else
            {
                //fetchRecentStories();
            }

            if (downloadedStories.Equals("0001234")) //means an exception occurred when downloading stories
            {
                //load 'backup'/default markers in since theres some error which stopped app from loading all the markers
                loadUpPhonyStories();
            }
            else
            {
                if (downloadedStories.Equals("") && !onlyRefresh)
                {
                    //MessageBox.Show("no results returned");
                    loadUpPhonyStories();
                }
                else
                {
                    if (!onlyRefresh)
                    {
                        //clear all existing stories for entire story list download
                        storiesArrayList = new ArrayList();
                        recentStoriesArrayList = new ArrayList();

                        string[] tempStoriesArray = downloadedStories.Split('\'');
                        //MessageBox.Show(downloadedStories);
                        int a = 0;
                        while (a < tempStoriesArray.Length - 1)
                        {
                            storiesArrayList.Add(new Story(Convert.ToInt32(tempStoriesArray[a]), Convert.ToInt32(tempStoriesArray[a + 1]),
                                Convert.ToInt32(tempStoriesArray[a + 2]), Convert.ToDouble(tempStoriesArray[a + 3]), Convert.ToDouble(tempStoriesArray[a + 4]),
                                tempStoriesArray[a + 5].ToString(), false));
                            a += 6;
                        }
                    }

                    Story[] randomStories;
                    Random random = new Random();

                    if (storiesArrayList.Count + recentStoriesArrayList.Count < MaxNumberTotalStoriesToShowAtATime)
                    {
                        randomStories = new Story[(storiesArrayList.Count + recentStoriesArrayList.Count)];
                    }
                    else
                    {
                        randomStories = new Story[MaxNumberTotalStoriesToShowAtATime];
                    }


                    //randomly chooses stories from the respective lists
                    selectRandomItems(museumStoriesArrayList, randomStories, MaxNumberMuseumStoriesToShowAtATime);
                    selectRandomItems(recentStoriesArrayList, randomStories, MaxNumberRecentStoriesToShowAtATime);

                    //only add random stories if there has been any recent activity
                    //noRecentStoriesForALongTime will be set to false by code when there is activity..
                    if (!noRecentStoriesForALongTime)
                    {
                        selectRandomItems(storiesArrayList, randomStories, MaxNumberRandomStoriesToShowAtATime);
                    }


                    //sort the story array so that the objects with higher latitudes are listed first in the array
                    //uses the selection sort algorithm
                    int minNum;
                    for (int j = 0; j < randomStories.Length - 1; j++)
                    {
                        if (randomStories[j] != null)
                        {
                            //assign value to value of the incrementer
                            minNum = j;

                            for (int k = j + 1; k < randomStories.Length; k++)
                            {
                                if (randomStories[k] != null)
                                {
                                    //check this index of the array to see if it's smaller
                                    //than the index of the second loop
                                    if (randomStories[minNum].StoryLatitude < randomStories[k].StoryLatitude)
                                    {
                                        minNum = k;
                                    }
                                }
                            }

                            if (minNum != j)//sorts the objects
                            {
                                Story tempStory;

                                //temporary hold for attributes of object at the previous index
                                int stID = randomStories[j].StoryID;
                                int catID = randomStories[j].CategoryID;
                                int timeID = randomStories[j].TimeSlotID;
                                double latitude = randomStories[j].StoryLatitude;
                                double longitude = randomStories[j].StoryLongitude;
                                string content = randomStories[j].StoryContent;
                                bool bubbleShow = randomStories[j].ShowBubble;


                                //sets the new object to the proper place in the array
                                tempStory = new Story(randomStories[minNum].StoryID, randomStories[minNum].CategoryID, randomStories[minNum].TimeSlotID,
                                    randomStories[minNum].StoryLatitude, randomStories[minNum].StoryLongitude, randomStories[minNum].StoryContent, randomStories[minNum].ShowBubble);
                                randomStories[j] = tempStory;

                                //swaps the old objects position with the previous
                                tempStory = new Story(stID, catID, timeID, latitude, longitude, content, bubbleShow);
                                randomStories[minNum] = tempStory;
                            }

                        }
                    }

                    timerAddStories = randomStories;
                    timerAddStoryIndex = 0;
                    markerRedrawTimer.Tick += new EventHandler(addMarkerTimer_Tick);
                    markerRedrawTimer.Interval = 1;
                    markerRedrawTimer.Start();

                }
            }
        }


        /// <summary>
        /// This method fetches from the database the objects specified by the caller (timeslots, categories, stories)
        /// and adds all the data into a single string.
        /// The caller methods 'initialiseCategories()', 'initialiseTimeSlots()', or 'addDownloadedMarkersToMap()' split
        /// this string into pieces, making the data more accessible.
        /// The split string array is then processed by the application as necessary.
        /// </summary>
        /// <returns>a string which contains all the respective objects from the database</returns>
        private string downloadRequestFromDB(string downloadRequestName)
        {
            try
            {
                WebRequest request = WebRequest.Create(RequestPageURL);
                request.Method = "POST";
                string postData = downloadRequestName;
                byte[] byteArray = Encoding.UTF8.GetBytes(postData);
                request.ContentType = "application/x-www-form-urlencoded";
                request.ContentLength = byteArray.Length;
                Stream dataStream = request.GetRequestStream();
                dataStream.Write(byteArray, 0, byteArray.Length);
                dataStream.Close();
                WebResponse response = request.GetResponse();
                dataStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(dataStream);
                string responseFromServer = reader.ReadToEnd();

                reader.Close();
                dataStream.Close();
                response.Close();

                return responseFromServer;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return "0001234";
            }
        }


        /// <summary>
        /// Load up some phony stories
        /// Originally this methods purpose was for demonstration use when app was not connecting to db.
        /// Now, this method is here to load a some stories in case that the connection to database fails.. or any other error
        /// </summary>
        private void loadUpPhonyStories()
        {
            MessageBox.Show("TODO : this method (loadUpPhonyStories - (offline management) needs to be revised!");
            //PointLatLng storyPosition;
            ////story 1
            //storyPosition = new PointLatLng(-36.881380148988356, 174.7067890442047);
            //string storyText = "Auckland Museum visited Unitec Design & Computer Science Groups \nand were amazed by their progress on the Ahi Kaa Mapping Project.";
            //addMarkerToMap(storyPosition, storyText, HOUSEID);

            ////story 2
            //storyPosition = new PointLatLng(-36.854917942560306, 174.7628042972717);
            //storyText = "We brought our wedding and engagement rings here they had \na better selection than around home. We have been married for 25 years.";
            //addMarkerToMap(storyPosition, storyText, RESTAURANTID);

            ////story 3
            //storyPosition = new PointLatLng(-36.830722284660744, 174.7456381595764);
            //storyText = "The first time we drove over Harbour Bridge. It was so exciting \nand there was a toll.";
            //addMarkerToMap(storyPosition, storyText, ROMANCEID);

            ////story 4
            //storyPosition = new PointLatLng(-36.8528747443828, 174.76333001023863);
            //storyText = "I remember watching Uncle Tom's children's choir singing in \nAuckland Town Hall. I cried through some of the songs.";
            //addMarkerToMap(storyPosition, storyText, ROMANCEID);
        }


        /// <summary>
        /// Add markers to the map
        /// </summary>
        /// <param name="position"></param>
        /// <param name="storyText"></param>
        /// <param name="storyCategory"></param>
        private void addMarkerToMap(PointLatLng position, string storyText, int storyCategory, int storyID, bool shouldWeShowBubble)
        {
            //Console.WriteLine("addMarkerToMap_1");
            GMapMarker marker = new GMapMarker(position);
            marker.ZIndex = SQUARE_Z_INDEX + 10;
            previousPosition = currentPosition;

            Category cat;
            int indexOfThisStoryCategory = 0;
            bool matchFound = false;
            for (int i = 0; i < categoriesArrayList.Count; i++)
            {
                cat = (Category)categoriesArrayList[i];
                if (cat.CategoryID == storyCategory)
                {
                    indexOfThisStoryCategory = i;
                    matchFound = true;
                }
            }
            if (matchFound)
            {
                cat = (Category)categoriesArrayList[indexOfThisStoryCategory];
                marker.Shape = new CustomMarker(this, marker, storyText, cat.MarkerImageSourceNormal, storyID, shouldWeShowBubble);

                MainMap.Markers.Add(marker);
                //Console.WriteLine("addMarkerToMap_2");
            }
        }


        /// <summary>
        /// This method will find an eligible story to display
        ///
        /// </summary>
        private void showStoryTextLoop()
        {
            DateTime temp = DateTime.Now;
            int tempStoryID = -1;

            //try for recent story first
            foreach (Story recent in recentStoriesArrayList)
            {
                for (int l = 0; l < timerAddStories.Length; l++)
                {
                    if (timerAddStories[l] != null)
                    {
                        if ((recent.LastView.CompareTo(temp) < 0) && (timerAddStories[l].StoryID == recent.StoryID))//if earlier than last view then we want it
                        {
                            temp = recent.LastView;
                            tempStoryID = recent.StoryID;
                        }
                    }
                }
            }

            if (tempStoryID == -1)//if no recent story, then get least recent museum story
            {
                foreach (Story museum in museumStoriesArrayList)
                {
                    for (int l = 0; l < timerAddStories.Length; l++)
                    {
                        if (timerAddStories[l] != null)
                        {
                            if ((museum.LastView.CompareTo(temp) < 0) && (timerAddStories[l].StoryID == museum.StoryID))//if earlier than last view then we want it
                            {
                                temp = museum.LastView;
                                tempStoryID = museum.StoryID;
                            }
                        }
                    }
                }
            }

            

            if (tempStoryID != -1)//if there is a story to show
            {
                for (int i = 0; i < MainMap.Markers.Count; i++)
                {
                    if ((int)(MainMap.Markers[i].Shape as CustomMarker).storyID == tempStoryID)
                    {
                        (MainMap.Markers[i].Shape as CustomMarker).ShowMarkerBubble();//show story

                        //set last view on used story
                        foreach (Story recent in recentStoriesArrayList)
                        {
                            if (recent.StoryID == tempStoryID)
                            {
                                recent.LastView = DateTime.Now;
                                break;
                            }
                        }
                        foreach (Story museum in museumStoriesArrayList)
                        {
                            if (museum.StoryID == tempStoryID)
                            {
                                museum.LastView = DateTime.Now;
                                break;
                            }
                        }
                        break;
                    }
                }
            }

        }


        /// <summary>
        /// Takes an arraylist of items, and randomly chooses a specified number to add
        /// to the specified object array
        /// </summary>
        /// <param name="listToSelectFrom">The arraylist of items to choose from</param>
        /// <param name="listToAddTo">The array of object to add the selected items to</param>
        /// <param name="numberToSelect">The number of items to select</param>
        private void selectRandomItems(ArrayList listToSelectFrom, object[] listToAddTo, int numberToSelect)
        {
            //to choose a certain number of any of these stories
            ArrayList testList = (ArrayList)listToSelectFrom.Clone();
            Random random = new Random();
            int totalNumber = listToSelectFrom.Count;
            int count = 0;
            int index = 0;
            int numOfNullIndices = 0;

            for (int i = 0; i < listToAddTo.Length; i++)
            {
                if (listToAddTo[i] != null)
                {
                    index++;
                }
                else
                {
                    numOfNullIndices++;
                }
            }

            if (numberToSelect > totalNumber)
            {
                numberToSelect = totalNumber;
            }

            if (numberToSelect > numOfNullIndices)
            {
                numberToSelect = numOfNullIndices;
            }

            while (totalNumber > 0 && numberToSelect > 0)
            {
                double rndm = random.NextDouble();
                if (rndm <= ((double)numberToSelect / (double)totalNumber)) //true then select
                {
                    listToAddTo[index] = (object)testList[count];
                    testList.RemoveAt(count);
                    totalNumber--;
                    numberToSelect--;
                    index++;
                }

                count++;
                if (count >= totalNumber)
                {
                    count = 0;
                }
            }
        }

        #region message queuing

        private void queue_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e)
        {
            if (e.Message.Label.Equals("UPDATE"))
            {
                //lock (sender)
                //{
                // The message is plain text.
                //string text = (string)e.Message.Body;
                Console.WriteLine("Message received");

                try
                {
                    //Set the formatter for the message.
                    e.Message.Formatter = new System.Messaging.XmlMessageFormatter(new Type[1] { typeof(MessageContent) });

                    //Get the MessageContent object out of the message.
                    MessageContent content = (MessageContent)e.Message.Body;

                    messageReceiveStory = content.Story;
                }
                catch (Exception e2)
                {
                    System.Console.WriteLine(e2.ToString() + "There is an ERROR!");
                }
                finally
                {
                    thereIsNewStoryWaitingToBeAdded = true;
                }

                //}
            }
        }

        /// <summary>
        /// this method initialises a message queue so that we can
        /// achieve inter-process communication
        /// </summary>
        private bool CreateQueue()
        {
            try
            {
                queue = new MessageQueue(queueName);
                queue.ReceiveCompleted += new ReceiveCompletedEventHandler(queue_ReceiveCompleted);
                queue.BeginReceive();
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return false;
            }
        }


        //clear message queue
        private void removeMessages()
        {
            //Purge all messages from the queue.
            try
            {
                queue.Purge();
                Console.WriteLine("Messages purged", "MSMQ");
            }
            catch (Exception e)
            {
                Console.WriteLine("Messages NOT purged", "MSMQ");
                Console.WriteLine(e.Message);
            }
        }

        #endregion

        #region timers


        /// <summary>
        /// timer to manage how often the text bubble will display
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void showStoryBubbleTimer_Tick(object sender, EventArgs e)
        {
            if (showStoryBubbleTimer.Enabled)
            {
                if (avoidTimerConflicts)
                {
                    avoidTimerConflicts = false;
                    showStoryBubbleTimer.Stop();
                    showStoryTextLoop();
                    showStoryBubbleTimer.Interval = 30000;
                    showStoryBubbleTimer.Start();
                    avoidTimerConflicts = true;
                }
                else//defer event by 1 second
                {
                    showStoryBubbleTimer.Stop();
                    showStoryBubbleTimer.Interval = 1000;

                    showStoryBubbleTimer.Start();
                }
            }
        }

        /// <summary>
        /// winforms timer which acts when a the message queue has a new message
        /// and then updates the story list
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void pushNewStoryTimer_Tick(object sender, EventArgs e)
        {
            if (thereIsNewStoryWaitingToBeAdded)
            {
                avoidTimerConflicts = false;
                if (MaxNumberRecentStoriesToShowAtATime > 0)
                {
                    fetchRecentStories();
                }
                idleTimer.Stop();
                reloadStories();
                thereIsNewStoryWaitingToBeAdded = false;
                idleTimer.Start();
                avoidTimerConflicts = true;
                queue.BeginReceive();
            }
        }

        /// <summary>
        /// winforms timer which determines when the program should ,....
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void idleTimer_Tick(object sender, EventArgs e)
        {
            if (idleTimer.Enabled)
            {
                if (avoidTimerConflicts)
                {
                    avoidTimerConflicts = false;
                    idleTimer.Stop();
                    //MessageBox.Show(IdleHandler.GetIdleTime() + "");
                    //refuses to run on different threads -- that is reason for System.Windows.Forms.Timer

                    reloadStories();
                    //MainMap.Markers.Clear(); //to clear current story markers on map
                    //addDownloadedMarkersToMap(true); //to reload markers only NOT download new stories
                    idleTimer.Interval = storyCycleInterval;
                    idleTimer.Start();
                    avoidTimerConflicts = true;
                }
                else//defer event by 12 seconds
                {
                    idleTimer.Stop();
                    idleTimer.Interval = 12000;
                    idleTimer.Start();
                }
            }
        }

        /// <summary>
        /// WinForms timer (on main thread), which removes markers from the map one at a time
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void removeMarkerTimer_Tick(object sender, EventArgs e)
        {
            //remove stories one at a time for animation purposes
            if (MainMap.Markers.Count > 0)
            {
                (MainMap.Markers[0].Shape as CustomMarker).CloseAnimation();//need animation to work
                //Console.WriteLine(MainMap.Markers.Count.ToString());
                MainMap.Markers.RemoveAt(0);
            }
            else
            {
                //if all done
                markerRedrawTimer.Stop();
                markerRedrawTimer.Tick -= new EventHandler(removeMarkerTimer_Tick);

                addDownloadedMarkersToMap(true); //to reload markers only NOT download new stories
                //loadAuckZone();
            }
        }


        /// <summary>
        /// WinForms timer (on main thread), which adds markers from the map one at a time
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void addMarkerTimer_Tick(object sender, EventArgs e)
        {
            //Console.WriteLine("gets here_addMarkerTimer_Tick");
            if (timerAddStories != null && timerAddStoryIndex < timerAddStories.Length)
            {
                if (timerAddStories[timerAddStoryIndex] != null)
                {
                    PointLatLng storyPosition = new PointLatLng(timerAddStories[timerAddStoryIndex].StoryLatitude, timerAddStories[timerAddStoryIndex].StoryLongitude);
                    addMarkerToMap(storyPosition, timerAddStories[timerAddStoryIndex].StoryContent, timerAddStories[timerAddStoryIndex].CategoryID, timerAddStories[timerAddStoryIndex].StoryID, timerAddStories[timerAddStoryIndex].ShowBubble);
                    timerAddStories[timerAddStoryIndex].ShowBubble = false;
                }
                timerAddStoryIndex++;
            }
            else//no more stories
            {
                markerRedrawTimer.Stop();
                markerRedrawTimer.Tick -= new EventHandler(addMarkerTimer_Tick);
                timerAddStoryIndex = 0;
            }
        }

        #endregion

    }
}